// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Giovanni-1-

//@version=6
indicator("Swing Lines MTF", overlay = true, max_lines_count = 500)

// --- INPUTS — TF1 ---
g1 = "--- [TF1] ---"
tf1_tf           = input.timeframe("60",                 title = "Timeframe", group = g1)
tf1_show         = input.bool(true,                           title = "Show Swing TF1",  inline = "0", group = g1)
tf1_colorH       = input.color(color.new(color.black, 0),  title = "",          inline = "0", group = g1)
tf1_colorL       = input.color(color.new(color.black, 0),  title = "",          inline = "0", group = g1)
tf1_tagShow      = input.bool(false,                          title = "Txt",       inline = "0", group = g1)
tf1_width         = input.int(1,                               title = "   Width",     inline = "1", group = g1)
tf1_styleValid   = input.string("Solid",                     title = "| Style",   inline = "1", group = g1, options = ["Solid","Dashed","Dotted"])
tf1_maxLines     = input.int(10,                             title = "| Max",     inline = "1", group = g1)

tf1_showbWick    = input.bool(true,           title = "Broken by Wick", inline = "2", group = g1)
tf1_bHWickColor  = input.color(color.blue,  title = "",                        inline = "2", group = g1)
tf1_bLWickColor  = input.color(color.blue,  title = "",                        inline = "2", group = g1)
tf1_bWickWidth  = input.int(1,                title = "   Width",                   inline = "W2", group = g1)
tf1_bWickStyle  = input.string("Dotted",      title = "| Style",                   inline = "W2", group = g1, options = ["Solid","Dashed","Dotted"])
tf1_bWickMax    = input.int(5,                title = "| Max",                     inline = "W2", group = g1)

tf1_showbClose   = input.bool(true,           title = "Broken by Close", inline = "3", group = g1)
tf1_bHCloseColor = input.color(color.green, title = "",                inline = "3", group = g1)
tf1_bLCloseColor = input.color(color.red,   title = "",                inline = "3", group = g1)
tf1_bCloseWidth = input.int(1,                title = "   Width",                       inline = "B3", group = g1)
tf1_bCloseStyle = input.string("Solid",      title = "| Style",                   inline = "B3", group = g1, options = ["Solid","Dashed","Dotted"])
tf1_bCloseMax   = input.int(5,                title = "| Max",                     inline = "B3", group = g1)

// --- INPUTS — TF2 ---
g2 = "--- [TF2] ---"
tf2_tf           = input.timeframe("240",                    title = "Timeframe", group = g2)
tf2_show         = input.bool(true,                           title = "Show Swing TF2",  inline = "0", group = g2)
tf2_colorH       = input.color(color.new(color.gray, 0),   title = "",          inline = "0", group = g2)
tf2_colorL       = input.color(color.new(color.gray, 0),   title = "",          inline = "0", group = g2)
tf2_tagShow      = input.bool(false,                          title = "Txt",       inline = "0", group = g2)
tf2_width         = input.int(1,                               title = "   Width",     inline = "1", group = g2)
tf2_styleValid   = input.string("Solid",                     title = "| Style",   inline = "1", group = g2, options = ["Solid","Dashed","Dotted"])
tf2_maxLines     = input.int(10,                             title = "| Max",     inline = "1", group = g2)

tf2_showbWick    = input.bool(true,           title = "Broken by Wick", inline = "2", group = g2)
tf2_bHWickColor  = input.color(color.blue,  title = "",                        inline = "2", group = g2)
tf2_bLWickColor  = input.color(color.blue,  title = "",                        inline = "2", group = g2)
tf2_bWickWidth  = input.int(1,                title = "   Width",                   inline = "W2", group = g2)
tf2_bWickStyle  = input.string("Dotted",      title = "| Style",                   inline = "W2", group = g2, options = ["Solid","Dashed","Dotted"])
tf2_bWickMax    = input.int(5,                title = "| Max",                     inline = "W2", group = g2)

tf2_showbClose   = input.bool(true,           title = "Broken by Close", inline = "3", group = g2)
tf2_bHCloseColor = input.color(color.green, title = "",                inline = "3", group = g2)
tf2_bLCloseColor = input.color(color.red,   title = "",                inline = "3", group = g2)
tf2_bCloseWidth = input.int(1,                title = "   Width",                       inline = "B3", group = g2)
tf2_bCloseStyle = input.string("Solid",      title = "| Style",                   inline = "B3", group = g2, options = ["Solid","Dashed","Dotted"])
tf2_bCloseMax   = input.int(5,                title = "| Max",                     inline = "B3", group = g2)

// --- TYPES ---
type SwingLevel
    line   ln
    float  price
    int    bar
    bool   isHigh
    bool   broken
    int    brokenBy
    label  lbl

type TFConfig
    string tf
    bool   show
    color  colorH
    color  colorL
    bool   tagShow
    int    width
    string styleValid
    int    maxLines
    color  bHWickColor
    string bHWickStyle
    int    bHWickWidth
    int    bHWickMax
    color  bHCloseColor
    string bHCloseStyle
    int    bHCloseWidth
    int    bHCloseMax
    color  bLWickColor
    string bLWickStyle
    int    bLWickWidth
    int    bLWickMax
    color  bLCloseColor
    string bLCloseStyle
    int    bLCloseWidth
    int    bLCloseMax

type TFState
    SwingLevel[] activeHighs
    SwingLevel[] activeLows
    SwingLevel[] brokenHighsWick
    SwingLevel[] brokenHighsClose
    SwingLevel[] brokenLowsWick
    SwingLevel[] brokenLowsClose

// --- METHODS ---
method toStyle(string self) =>
    switch self
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        =>          line.style_solid

method push_x2(SwingLevel self) =>
    if not na(self.ln)
        self.ln.set_x2(bar_index)

method paint(SwingLevel self, color c, string s, int w) =>
    if not na(self.ln)
        self.ln.set_color(c)
        self.ln.set_style(s.toStyle())
        self.ln.set_width(w)

method checkBreak(SwingLevel self, float h_ltf, float l_ltf, float c_htf) =>
    int result = 0
    if not self.broken
        if self.isHigh
            if c_htf > self.price      
                result := 2
            else if h_ltf > self.price 
                result := 1
        else
            if c_htf < self.price      
                result := 2
            else if l_ltf < self.price 
                result := 1
    if result != 0
        self.broken   := true
        self.brokenBy := result
    result

// --- HELPERS ---
purge(SwingLevel[] arr, int maxSize) =>
    if arr.size() > maxSize
        SwingLevel oldest = arr.shift()
        if not na(oldest.ln)
            line.delete(oldest.ln)
        if not na(oldest.lbl)
            label.delete(oldest.lbl)
    true 

spawnLevel(float price, bool isHigh, TFConfig cfg, TFState st, string txt_tf) =>
    // Il CISD è sempre a sinistra del High/Low (bar_index - 1)
    line ln = line.new(x1 = bar_index - 1, 
                       y1 = price, 
                       x2 = bar_index, 
                       y2 = price,
                       color = isHigh ? cfg.colorH : cfg.colorL,
                       width = cfg.width,
                       style = cfg.styleValid.toStyle())
    
    label my_lbl = na
    if cfg.tagShow
        my_lbl := label.new(x = bar_index, 
                            y = price, 
                            text = txt_tf,
                            xloc = xloc.bar_index,
                            style = label.style_label_left, 
                            color = color.new(color.white, 100),
                            textcolor = isHigh ? cfg.colorH : cfg.colorL,
                            size = size.tiny)

    // Creazione oggetto SwingLevel
    SwingLevel sl = SwingLevel.new(ln, price, bar_index - 1, isHigh, false, 0, my_lbl)
    
    // Gestione Array
    if isHigh
        st.activeHighs.push(sl)
        purge(st.activeHighs, cfg.maxLines)
    else
        st.activeLows.push(sl)
        purge(st.activeLows, cfg.maxLines)
    
    true 

updateLevels(TFState st, TFConfig cfg, float h_ltf, float l_ltf, float c_htf) =>
    // --- Gestione Highs ---
    int i = 0
    while i < st.activeHighs.size()
        SwingLevel sl = st.activeHighs.get(i)
        int broke = sl.checkBreak(h_ltf, l_ltf, c_htf)
        if broke != 0
            if not na(sl.lbl)
                label.delete(sl.lbl)
                sl.lbl := na
            
            if broke == 1
                sl.paint(cfg.bHWickColor, cfg.bHWickStyle, cfg.bHWickWidth)
                st.brokenHighsWick.push(sl)
                purge(st.brokenHighsWick, cfg.bHWickMax)
            else
                sl.paint(cfg.bHCloseColor, cfg.bHCloseStyle, cfg.bHCloseWidth)
                st.brokenHighsClose.push(sl)
                purge(st.brokenHighsClose, cfg.bHCloseMax)
            
            sl.push_x2()
            st.activeHighs.remove(i)
        else
            if not na(sl.lbl)
                label.set_x(sl.lbl, bar_index)
            sl.push_x2()
            i += 1
    
    // --- Gestione Lows ---
    int j = 0
    while j < st.activeLows.size()
        SwingLevel sl = st.activeLows.get(j)
        int broke = sl.checkBreak(h_ltf, l_ltf, c_htf)
        if broke != 0
            if not na(sl.lbl)
                label.delete(sl.lbl)
                sl.lbl := na

            if broke == 1
                sl.paint(cfg.bLWickColor, cfg.bLWickStyle, cfg.bLWickWidth)
                st.brokenLowsWick.push(sl)
                purge(st.brokenLowsWick, cfg.bLWickMax)
            else
                sl.paint(cfg.bLCloseColor, cfg.bLCloseStyle, cfg.bLCloseWidth)
                st.brokenLowsClose.push(sl)
                purge(st.brokenLowsClose, cfg.bLCloseMax)
            
            sl.push_x2()
            st.activeLows.remove(j)
        else
            if not na(sl.lbl)
                label.set_x(sl.lbl, bar_index)
            sl.push_x2()
            j += 1
    true // Ritorno booleano esplicito

processTF(TFConfig cfg, TFState st, bool newBar, float H2, float H1, float H0, float L2, float L1, float L0, float C_live, string txt_tf) =>
    if cfg.show
        if newBar
            // Logica Pivot: H1 è il massimo tra H2 e H0
            if H2 < H1 and H1 > H0
                spawnLevel(H1, true, cfg, st, txt_tf)
            if L2 > L1 and L1 < L0
                spawnLevel(L1, false, cfg, st, txt_tf)
        // Aggiornamento costante (spostamento x2 e check rottura)
        updateLevels(st, cfg, high, low, C_live)
    true // Ritorno booleano esplicito

// --- INIT ---
cfg1 = TFConfig.new(tf1_tf, tf1_show, tf1_colorH, tf1_colorL, tf1_tagShow, tf1_width, tf1_styleValid, tf1_maxLines, tf1_bHWickColor, tf1_bWickStyle, tf1_bWickWidth, tf1_bWickMax, tf1_bHCloseColor, tf1_bCloseStyle, tf1_bCloseWidth, tf1_bCloseMax, tf1_bLWickColor, tf1_bWickStyle, tf1_bWickWidth, tf1_bWickMax, tf1_bLCloseColor, tf1_bCloseStyle, tf1_bCloseWidth, tf1_bCloseMax)
cfg2 = TFConfig.new(tf2_tf, tf2_show, tf2_colorH, tf2_colorL, tf2_tagShow, tf2_width, tf2_styleValid, tf2_maxLines, tf2_bHWickColor, tf2_bWickStyle, tf2_bWickWidth, tf2_bWickMax, tf2_bHCloseColor, tf2_bCloseStyle, tf2_bCloseWidth, tf2_bCloseMax, tf2_bLWickColor, tf2_bWickStyle, tf2_bWickWidth, tf2_bWickMax, tf2_bLCloseColor, tf2_bCloseStyle, tf2_bCloseWidth, tf2_bCloseMax)

var st1 = TFState.new(array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>())
var st2 = TFState.new(array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>(), array.new<SwingLevel>())

// --- SECURITY ---
[t1H2, t1H1, t1H0, t1L2, t1L1, t1L0, t1Cl] = request.security(syminfo.tickerid, tf1_tf, [high[2], high[1], high[0], low[2], low[1], low[0], close], lookahead = barmerge.lookahead_on)
[t2H2, t2H1, t2H0, t2L2, t2L1, t2L0, t2Cl] = request.security(syminfo.tickerid, tf2_tf, [high[2], high[1], high[0], low[2], low[1], low[0], close], lookahead = barmerge.lookahead_on)

tf1_nb = ta.change(time(tf1_tf)) != 0
tf2_nb = ta.change(time(tf2_tf)) != 0

processTF(cfg1, st1, tf1_nb, t1H2, t1H1, t1H0, t1L2, t1L1, t1L0, t1Cl, tf1_tf)
processTF(cfg2, st2, tf2_nb, t2H2, t2H1, t2H0, t2L2, t2L1, t2L0, t2Cl, tf2_tf)